/**
 * Copyright [2010] Delta Air Lines, Inc. All Rights Reserved Access,
 * Modification, or Use Prohibited Without Express Permission of Delta Air
 * Lines.
 */
package com.delta.rafg.core.log;


import java.io.ByteArrayInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import com.delta.rafg.core.componentbase.exception.DLException;
import com.delta.rafg.core.log.impl.LogQueuerDummy;
import com.delta.rafg.core.log.impl.LogQueuerJMS;
import com.delta.rafg.core.log.impl.LogQueuerJMSTibcoEMS;
import com.delta.rafg.coredata.AObject;
import com.delta.rafg.coredata.ClientData;
import com.delta.rafg.coredata.XMLReqString;
import com.delta.rafg.coredata.XMLResString;


/**
 * @author RAFG Development Team (D9785692)
 */
public class RAFGLogger extends AObject {

    /**
     * serialVersion property.
     */
    private static final long serialVersionUID = 7468925415296228953L;

    public static final int OFF = LogData.OFF;

    public static final int FATAL = LogData.FATAL;

    public static final int ERROR = LogData.ERROR;
    public static final int ERROR_ONLY = LogData.ERROR_ONLY;

    public static final int STATS = LogData.STATS;
    public static final int STATS_ONLY = LogData.STATS_ONLY;
    
    public static final int WARNING = LogData.WARNING;

    public  static final int INFO = LogData.INFO;

    public static final int ALL = LogData.ALL;

    /**
     * logLevel property.
     */
    private static int logLevel = ERROR;
    
    public static String LOG_LEVEL_KEY = "AXS_CFG_log.level";
    
    public static String QUEUER_TYPE_KEY = "AXS_CFG_log.queuer";
    
    public static String TIBCOEMS = "TIBCOEMS";
    
    public static String JMS = "JMS";
    
    /**
     * logQueuer property.
     */
    static ILogQueuer logQueuer = new LogQueuerDummy();
    static{
    	String queuertype = System.getProperty("com.delta.rafg.log.queuer");
    	
    	setLogQueuerType(queuertype);
    	
    	String environment = System.getProperty("com.delta.rafg.core.config.environment");
    	if( (environment==null || environment.equalsIgnoreCase("Dev") 
    			|| environment.equalsIgnoreCase("Development")) ){
    		setLogLevel(ALL);
    	}
    }

    /**
     * Do not hold the thread. We could used websphere's Asynchronous Beans.
     * @param pLogData logdata
     */
    private static void doLoggingAsync(final LogData pLogData) {

        new Thread(new Runnable() {

            @Override
            public void run() {

                logQueuer.queueToLog(pLogData);
            }
        }).start();
    }

    /**
     * Method error.
     * @param pFreeText message
     * @param pRequest response
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData error(final String pFreeText,
        final Object... pOtherObjects) {

        return log(ERROR_ONLY, (Throwable)null, pFreeText, pOtherObjects);
    }

    /**
     * LogData Constructor.
     * @param pException message
     * @param pRequest request
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData error(final Throwable pException,
        final Object... pOtherObjects) {
        return log(ERROR_ONLY, pException,(String) null, pOtherObjects);
    }

    /**
     * Method returns error log data.
     * @param pException exception
     * @param pFreeText freetext
     * @param pRequest Clientdata
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData error(final Throwable pException,
        final String pFreeText, final Object... pOtherObjects) {

        return log(ERROR_ONLY, pException, pFreeText, pOtherObjects);
    }

    /**
     * Method returns exception log data.
     * @param pException message
     * @return LogData
     */
    public static LogData errorException(final Throwable pException) {
    	String aFreeText = null;
    	if( pException != null ){

        	if(pException.getClass().getSimpleName().equalsIgnoreCase("BusinessFault") || pException.getClass().getSimpleName().equalsIgnoreCase("SystemFault")){
        		aFreeText = pException.getClass().getSimpleName() + " - ";
        		aFreeText += getDataFromObject(pException, "FaultInfo.DetailedInfo.ErrorMessage");
        		aFreeText += getDataFromObject(pException, "FaultInfo.FaultDetailedInfo.ErrorMessage");
        	}
        	else
        		aFreeText = pException.toString();
    	}
    	LogData logDta = error(pException, aFreeText, null, (Object[])null);
    	//debugLogQueuer.info(logDta.getCodeLocation() + ":" + aFreeText);
        return logDta;
    }

    /**
     * Method returns exception log data.
     * @param pException message
     * @return LogData
     */
    public static LogData infoException(final Throwable pException) {
    	String aFreeText = null;
    	if( pException != null )
    		aFreeText = pException.toString();
    	LogData logDta = info(pException, aFreeText, null, (Object[])null);
    	//debugLogQueuer.info(logDta.getCodeLocation() + ":" + aFreeText);
        return logDta;
    }

    /**
     * Method returns fatal log data.
     * @param pFreeText exception
     * @param pRequest request
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData fatal(final String pFreeText,
        final Object... pOtherObjects) {

        return log(FATAL, (Throwable)null, pFreeText, pOtherObjects);
    }

    /**
     * Method returns fatal log data.
     * @param pException exception
     * @param pRequest request
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData fatal(final Throwable pException,
        final Object... pOtherObjects) {

        return log(FATAL, pException, (String) null, pOtherObjects);
    }

    /**
     * Method returns fatal log data.
     * @param pException exception
     * @param pRequest request
     * @param pFreeText freeText
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData fatal(final Throwable pException,
        final String pFreeText, final Object... pOtherObjects) {

        return log(FATAL, pException, pFreeText, pOtherObjects);
    }

    /***
     * This method is used to get logging level.
     * @return log
     */
    public static int getLogLevel() {

        return logLevel;
    }

    /**
     * This should only be used for logging SOA Request and Response in log file.
     * 	For logging via EMS - RafgLogger.stats should be used.
     * 
     * @param pFreeText
     * @return
     */
    public static boolean logReqRes(final String pFreeText, boolean pInsignificant) {
    	
    	boolean isLogged = false;
    	
    	if(logQueuer!=null && logQueuer instanceof LogQueuerDummy){
    		if(!pInsignificant){
    			isLogged = error(pFreeText, (Object[])null).isLogged();
    		}
    		else{
    			isLogged = info(pFreeText, (Object[])null).isLogged();
    			if(!isLogged){
    				error("\t\t\t\tReq/Res XML suppressed (Chg Logging = INFO to unsuppress)\n");
    			}
    		}
    	}
    	return isLogged;
    }

    public static LogData unLoggedData(int pLogLevel){
    	return new LogData(false, "Not Logged: " + getLogLevel() + "<>" + pLogLevel, (Serializable)null, (Serializable)null, (Object[])null);
    }
    
    //static LogQueuerDummy debugLogQueuer = new LogQueuerDummy();
    /**
     * Method returns fatal log data.
     * @param pFreeText freetext
     * @param pClientData clientdata
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData info(final String pFreeText) {
    	String codeLocation = LogData.determineCodeLocation();
        return info(codeLocation + "\n" + pFreeText, (Object[])null);
    }

    /**
     * Method returns fatal log data.
     * @param pFreeText freetext
     * @param pRequest request
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData info(final String pFreeText,
        final Object... pOtherObjects) {

        return log(INFO,(Throwable)null,  pFreeText, pOtherObjects);
    }

    /**
     * Method returns fatal log data.
     * @param pException exception
     * @param pRequest request
     * @param pFreeText freetext
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData info(final Throwable pException,
        final String pFreeText, final Object... pOtherObjects) {

        return log(INFO, pException, pFreeText, pOtherObjects);
    }

    /**
     * Method returns log's log data.
     * @param pLogLevel loglevel
     * @param pException exception
     * @param pFreeText freetext
     * @param pRequest clientdata
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    private static LogData log(final int pLogLevel, final Throwable pException,
        final String pFreeText, Object... pOtherObjects) {
    	
    	Serializable pRequest = null;
        Serializable pResponse = null;
        
        XMLReqString beReq = null;
        XMLResString beRes = null;
        
        
        //Determine the request and response objects from the dynamic arguments
		if (pOtherObjects != null && pOtherObjects.length > 0) {
			
	        List<Object> theRemainingObjs = new ArrayList<Object>();

	        for(Object theObj : pOtherObjects){
				if( theObj != null ){
					if( theObj instanceof XMLReqString && beReq == null ){
						beReq = (XMLReqString)theObj;
					}
					else if( theObj instanceof XMLResString  && beRes == null ){
						beRes = (XMLResString)theObj;
					}
					else if( theObj instanceof ClientData && pRequest == null ){
						pRequest = (ClientData) theObj;
					}
					else if( theObj instanceof Serializable &&  pRequest == null ){
						pRequest = (Serializable) theObj;
					}
					else if( theObj instanceof Serializable &&  pResponse == null ){
						pResponse = (Serializable) theObj;
					}
					else{
						theRemainingObjs.add(theObj);
					}
				}
				
				if( pRequest != null && pResponse != null && beReq != null && beRes != null){
					break;
				}
			}
	        
	        pOtherObjects = theRemainingObjs.toArray();
		}

        if (!shouldLog(pLogLevel)) { return unLoggedData(pLogLevel); }
        LogData theLogData = null;
        if (pException != null && pException instanceof DLException) {
            theLogData =
                new LogData(pFreeText, pException, pRequest, pResponse,
                    pOtherObjects);
            final LogData fromException =
                ((DLException) pException).getLogData();
            // logged already
            if (theLogData.equals(fromException)) { return fromException; }
        } else if (pException != null) {
            theLogData =
                new LogData(pFreeText, pException, pRequest, pResponse,
                    pOtherObjects);
        } else {
            theLogData =
                new LogData(pFreeText, pRequest, pResponse, pOtherObjects);
        }
        theLogData.setBackEndRequest(beReq);
        theLogData.setBackEndResponse(beRes);
        theLogData.setSeverity(pLogLevel);
        doLoggingAsync(theLogData);
        return theLogData;
    }

    /**
     * This method is used for returning a log level.
     * @param pLogLevel loglevel
     * @return loglevel
     */
    public static boolean shouldLog(final int pLogLevel) {

        return (getLogLevel() & pLogLevel) == pLogLevel;
    }

    /**
     * Method returns stat log level.
     * @param pFreeText freetext
     * @param pRequest request
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData stats(final String pFreeText,
        final Object... pOtherObjects) {

        return log(STATS_ONLY, (Throwable)null, pFreeText, pOtherObjects);
    }


    /**
     * Method returns warn log level.
     * @param pFreeText freetext
     * @param pRequest clientdata
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData warn(final String pFreeText,
        final Object... pOtherObjects) {

        return log(WARNING, (Throwable)null, pFreeText, pOtherObjects);
    }

    /**
     * Method returns warn log level.
     * @param pException exception
     * @param pRequest clientdata
     * @param pResponse response
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData warn(final Throwable pException,
        final Object... pOtherObjects) {

        return log(WARNING, pException, (String) null, pOtherObjects);
    }

    /**
     * Method returns warn log level.
     * @param pException exception
     * @param pRequest clientdata
     * @param pResponse response
     * @param pFreeText freetext
     * @param pOtherObjects otherobjects
     * @return log
     */
    public static LogData warn(final Throwable pException,
        final String pFreeText, final Object... pOtherObjects) {

        return log(WARNING, pException, pFreeText, pOtherObjects);
    }

    /**
     * sets loglevel.
     * @param pLogLevel loglevel
     */
    public static final void setLogLevel(final int pLogLevel) {

        if ((ALL & pLogLevel) == pLogLevel) {
            logLevel = pLogLevel;
        }
        
        configSystemLog(pLogLevel);
    }
    
    /**
     * Sets the LogQueuerType
     * 
     * @param pLogQueuerType
     */
    public static final void setLogQueuerType(String pLogQueuerType){
    	if(TIBCOEMS.equalsIgnoreCase(pLogQueuerType)){
    		logQueuer = new LogQueuerJMSTibcoEMS();
    	}
    	else if(JMS.equalsIgnoreCase(pLogQueuerType)){
    		logQueuer = new LogQueuerJMS();
    	}
    	else{
    		logQueuer = new LogQueuerDummy();
    	}
    }
    
    
    public static void configSystemLog(int pLogLevel) {
        try {
        	String theLogLevel = "OFF";
        	switch(pLogLevel){
				case LogData.INFO:
				case LogData.WARNING:
					theLogLevel = "INFO";
				break;
				
				case LogData.ERROR:
				case LogData.ERROR_ONLY:
					theLogLevel = "SEVERE";
				break;
					
				case LogData.STATS:
				case LogData.STATS_ONLY:
					theLogLevel = "WARNING";
				break;
					
				case LogData.FATAL:
					theLogLevel = "SEVERE";
				break;        	
	        }
        	//LogManager.getLogManager().reset();
			java.util.logging.LogManager.getLogManager().readConfiguration(
					new ByteArrayInputStream((".level=" + theLogLevel
							+ "\njava.util.logging.ConsoleHandler.level="
							+ theLogLevel + "\n").getBytes()));
		    
        } catch (Throwable e) {
              e.printStackTrace();
        }
  }
    
    /**
     * This will return the child, grand child, or great grand child and so on
     *  attribute from an object.
     *  Example: getDataFromObject(aGreatGrandpaObject, "Grandpa.Papa.Kid.Name")
     *  	will return aGreatGrandpaObject.getGrandpa().getPapa().getKid().getName()
     *  
     * 	A more useful Example:
     * 		getDataFromObject(aBusinessFault, "FaultInfo.DetailedInfo.ErrorMessage")
     * 		You can do aBusinessFault.getFaultInfo().getFaultDetailedInfo().getErrorMessage()
     * 		irrespective of the version of BusinessFault. 
     * 
     * @param pObject
     * @param pAttribute
     * @return
     */
    public static Object getDataFromObject(Object pObject, String pAttribute ){
    	
    	if(pObject == null || pAttribute == null )
    		return "NULL";
    	
		// Use aObject as the place holder for each object down rabbit hole.
		Object aObject = pObject;
		String theAttribute = pAttribute;
		
		// the idea is to break down nested attributes. E.g.: Grandpa.Papa.Kid.Name
		// 	It will do aObject.getGrandpa() ==> pResponse.getGrandpa()
		//		then aObject.getPapa() ==> pResponse.getGrandpa().getPapa()
		//			then aObject.getKid() ==> pResponse.getGrandpa().getPapa().getKid()
		//				then aObject.getName()	==> pResponse.getGrandpa().getPapa().getKid().getName()		
		StringTokenizer aSt = new StringTokenizer(theAttribute, ".");
		while (aSt.hasMoreTokens()) {
			String aClass = aSt.nextToken();
			try {
				aObject = aObject.getClass().getMethod("get" + aClass)
						.invoke(aObject, (Object[]) null);
			} catch (Exception e) {
				return "";
			}
		}
		
		//aObject should probably have pResponse.getGrandpa().getPapa().getKid().getName()
		return aObject;
    }    
    
}